/*
 * graph2D
 * Copyright (c) 2013 Shun Moriya <shun@mnu.sakura.ne.jp>
 *
 * The MIT License
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR
 * THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

#if !defined(___GRAPH2D_IDENTIFICATION_H___)
#include "identification.h"
#endif

#include "desktop.h"
#include "texture.h"
#include "textureContainer.h"

#include "colorAction.h"
#include "moveAction.h"
#include "moveOffsetAction.h"
#include "scaleAction.h"

namespace Graph2D
{
	enum TranslationAnimationType
	{
		TRANSLATION_ANIMATION_TYPE_NONE = 0,
		TRANSLATION_ANIMATION_TYPE_MOVE,
		TRANSLATION_ANIMATION_TYPE_CHASE
	};

	Object::Object()
		: type(TYPE_OBJECT)
		, identification(Identification::regist())
		, flags(COMPONENT_FLAGS_VISIBLE_BIT|COMPONENT_FLAGS_ENABLE_BIT)
		, texture(NULL)
		, data(0)
		, translationAnimationType(TRANSLATION_ANIMATION_TYPE_NONE)
	{
		color.set(1, 1, 1, 1);
		offset.zero();
		texturePosition.zero();
		textureSize.zero();

		memset(actions, 0, sizeof(actions));
	}

	Object::~Object()
	{
		for(size_t i = 0; i < sizeof(actions) / sizeof(actions[0]); ++i)
		{
			delete actions[i];
			actions[i] = NULL;
		}
	}

	Vector2 Object::getPosition() const						//!< ローカル座標を取得します
	{
		return position + offset;
	}

	void Object::setPosition(const Vector2& position)		//!< ローカル座標を設定します
	{
		this->position = position;
	}

	void Object::setPosition(const float x, const float y)	//!< ローカル座標を設定します
	{
		position.set(x, y);
	}

	const Vector2& Object::getSize() const					//!< サイズを取得します
	{
		return size;
	}

	void Object::setSize(const Vector2& size)
	{
		this->size = size;
	}

	void Object::setSize(const float w, const float h)		//!< サイズを設定します
	{
		size.set(w, h);
	}

	const Vector2& Object::getScale() const					//!< スケールを取得します
	{
		return scale;
	}

	void Object::setScale(const Vector2& scale, const float second)
	{
		if(!actions[Action::TYPE_SCALE])
		{
			actions[Action::TYPE_SCALE] = new ScaleAction();
		}

		ScaleAction* action = static_cast<ScaleAction*>(actions[Action::TYPE_SCALE]);
		if(action)
		{
			action->setup(this, scale, second);
		}
	}

	/*!
	 * スケールを設定します
	 */
	void Object::setScale(const float x, const float y, const float second)
	{
		setScale(Vector2(x, y), second);
	}

	const Color& Object::getColor() const
	{
		return color;
	}

	void Object::setColor(const Color& color, const float second)
	{
		if(!actions[Action::TYPE_COLOR])
		{
			actions[Action::TYPE_COLOR] = new ColorAction();
		}

		ColorAction* action = static_cast<ColorAction*>(actions[Action::TYPE_COLOR]);
		if(action)
		{
			action->setup(this, color, second);
		}
	}

	/*!
	 * カラーを設定します
	 */
	void Object::setColor(const float r, const float g, const float b, const float a, const float second)
	{
		setColor(Color(r, g, b, a), second);
	}

	//! テクスチャ貼り付け座標を取得します
	const Vector2& Object::getTexturePosition() const
	{
		return texturePosition;
	}

	//! テクスチャ貼り付け座標を設定します
	void Object::setTexturePosition(const Vector2& position)
	{
		texturePosition = position;
	}

	//! テクスチャ貼り付け座標を設定します
	void Object::setTexturePosition(const float x, const float y)
	{
		setTexturePosition(Vector2(x, y));
	}

	//! テクスチャ貼り付け座標を取得します
	const Vector2& Object::getTextureSize() const
	{
		return textureSize;
	}

	//! テクスチャ貼り付け座標を設定します
	void Object::setTextureSize(const Vector2& size)
	{
		textureSize = size;
	}

	//! テクスチャ貼り付け座標を設定します
	void Object::setTextureSize(const float w, const float h)
	{
		setTextureSize(Vector2(w, h));
	}

	GraphicDevice::BlendMode Object::getBlendMode() const		//!< ブレンドモードを取得します
	{
		return static_cast<GraphicDevice::BlendMode>(blendMode);
	}

	void Object::setBlendMode(const GraphicDevice::BlendMode blendMode)	//!< ブレンドモードを設定します
	{
		this->blendMode = static_cast<unsigned char>(blendMode);
	}

	//! 不透明状態を取得します
	bool Object::getOpaque() const
	{
		return (flags & COMPONENT_FLAGS_OPAQUE_BIT) ? true : false;
	}

	//! 不透明状態を設定します
	void Object::setOpaque(const bool opaque)
	{
		if(opaque)
			flags |= COMPONENT_FLAGS_OPAQUE_BIT;
		else
			flags &= ~COMPONENT_FLAGS_OPAQUE_BIT;
	}

	//! シザリング状態を取得します
	bool Object::getClipping() const
	{
		return (flags & COMPONENT_FLAGS_CLIPPING_BIT) ? true : false;
	}

	//! シザリング状態を設定します
	void Object::setClipping(const bool clipping)
	{
		if(clipping)
			flags |= COMPONENT_FLAGS_CLIPPING_BIT;
		else
			flags &= ~COMPONENT_FLAGS_CLIPPING_BIT;
	}

	//! 表示するか取得
	bool Object::getVisible() const
	{
		return (flags & COMPONENT_FLAGS_VISIBLE_BIT) ? true : false;
	}

	//! 表示するか設定
	void Object::setVisible(const bool visible)
	{
		if(visible)
			flags |= COMPONENT_FLAGS_VISIBLE_BIT;
		else
			flags &= ~COMPONENT_FLAGS_VISIBLE_BIT;
	}

	//! 有効・無効設定
	bool Object::getEnable() const
	{
		return (flags & COMPONENT_FLAGS_ENABLE_BIT) ? true : false;
	}

	//! 有効か取得
	void Object::setEnable(const bool enable)
	{
		if(enable)
			flags |= COMPONENT_FLAGS_ENABLE_BIT;
		else
			flags &= ~COMPONENT_FLAGS_ENABLE_BIT;
	}

	//! タッチ可能状態を取得します
	bool Object::getTouchable() const
	{
		return (flags & COMPONENT_FLAGS_TOUCHABLE_BIT) ? true : false;
	}

	//! タッチ可能状態を設定します
	void Object::setTouchable(const bool touchable)
	{
		if(touchable)
			flags |= COMPONENT_FLAGS_TOUCHABLE_BIT;
		else
			flags &= ~COMPONENT_FLAGS_TOUCHABLE_BIT;
	}

	//! オーナードローフラグ取得
	bool Object::getOwnerDraw() const
	{
		return (flags & COMPONENT_FLAGS_OWNERDRAW_BIT) ? true : false;
	}

	//! オーナードローフラグ設定
	void Object::setOwnerDraw(const bool ownerDraw)
	{
		if(ownerDraw)
			flags |= COMPONENT_FLAGS_OWNERDRAW_BIT;
		else
			flags &= ~COMPONENT_FLAGS_OWNERDRAW_BIT;
	}

	//! ユーザーデータを取得します
	unsigned Object::getUserData() const
	{
		return data;
	}

	//! ユーザーデータを設定します
	void Object::setUserData(const unsigned data)
	{
		this->data = data;
	}

	////////////////////////////////////////////////////////////////////////////////
	// color
	Color Object::getDrawColor(const DrawRect& drawRect) const
	{
		return getDrawColor(drawRect, color);
	}

	Color Object::getDrawColor(const DrawRect& drawRect, const Color& color) const
	{
		return drawRect.getDrawColor(color);
	}

	////////////////////////////////////////////////////////////////////////////////
	// texture
	bool Object::loadTexture(const std::string& filename)	//!< テクスチャを読み込みます
	{
		TextureContainer& builder = TextureContainer::getInstance();
		builder.unload(texture);
		texture = builder.load(filename);
		return texture != NULL;
	}

	void Object::unloadTexture()							//!< テクスチャを開放します
	{
		TextureContainer& builder = TextureContainer::getInstance();
		builder.unload(texture);
		texture = NULL;
	}

	Texture* Object::getTexture() const						//!< テクスチャを取得します
	{
		return texture;
	}

	void Object::bindTexture()
	{
		if(texture)
			texture->bind();
	}

	void Object::setTexture(Texture* texture)
	{
		if(this->texture != texture)
		{
			unloadTexture();

			this->texture = texture;
		}
	}

	////////////////////////////////////////////////////////////////////////////////
	void Object::move(const Vector2& position, const float second)
	{
		if(!actions[Action::TYPE_MOVE])
		{
			actions[Action::TYPE_MOVE] = new MoveAction();
		}

		MoveAction* action = static_cast<MoveAction*>(actions[Action::TYPE_MOVE]);
		if(action)
		{
			action->setup(this, position, second);
		}
	}

	void Object::move(const float x, const float y, const float second)
	{
		move(Vector2(x, y), second);
	}

	void Object::chase(const Vector2& position, const float rate)
	{
#if 1
		if(!actions[Action::TYPE_MOVE])
		{
			actions[Action::TYPE_MOVE] = new MoveAction();
		}

		MoveAction* action = static_cast<MoveAction*>(actions[Action::TYPE_MOVE]);
		if(action)
		{
			action->setup(this, position, rate);
		}
#else
		if(rate >= 1.0f)
		{
			this->position = position;
		}
		else
		{
			animationTargetPosition = position;
			animationPositionTime = rate;
			translationAnimationType = TRANSLATION_ANIMATION_TYPE_CHASE;
		}
#endif
	}

	void Object::chase(const float x, const float y, const float rate)
	{
		chase(Vector2(x, y), rate);
	}

	//! 到着したか調べます
	bool Object::arrived() const
	{
		return translationAnimationType == TRANSLATION_ANIMATION_TYPE_NONE;
	}

	//! 垂直ジャンプします
	void Object::jump(const float height, const float second)
	{
		if(!actions[Action::TYPE_MOVE_OFFSET])
		{
			actions[Action::TYPE_MOVE_OFFSET] = new MoveOffsetAction();
		}

		MoveOffsetAction* action = static_cast<MoveOffsetAction*>(actions[Action::TYPE_MOVE_OFFSET]);
		if(action)
		{
			action->jump(this, height, second);
		}
	}

	//! 振動します
	void Object::shake(const float amplitude, const float second)
	{
		if(!actions[Action::TYPE_MOVE_OFFSET])
		{
			actions[Action::TYPE_MOVE_OFFSET] = new MoveOffsetAction();
		}

		MoveOffsetAction* action = static_cast<MoveOffsetAction*>(actions[Action::TYPE_MOVE_OFFSET]);
		if(action)
		{
			action->shake(this, amplitude, second);
		}
	}

	void Object::setAnimation(AnimationAction* animationAction)
	{
	}

	void Object::setAnimation(const size_t patternCount, const Animation* animation, const bool loop)
	{
		if(!actions[Action::TYPE_ANIMATION])
		{
			actions[Action::TYPE_ANIMATION] = new AnimationAction();
		}

		AnimationAction* action = static_cast<AnimationAction*>(actions[Action::TYPE_ANIMATION]);
		if(action)
		{
			action->setup(patternCount, animation, loop);
		}
	}

	////////////////////////////////////////////////////////////////////////////////
	// event
	/*!
	 * 更新イベント
	 */
	void Object::onUpdate(const UpdateInfomation& updateInfomation)
	{
		// 各アクションを更新
		for(size_t i = 0; i < sizeof(actions) / sizeof(actions[0]); ++i)
		{
			Action* action = actions[i];
			if(action && action->running())
			{
				if(action->onUpdate(this, updateInfomation))
				{
				}
			}
		}
#if 0
		// 位置アニメーションの更新
		switch(translationAnimationType)
		{
		case TRANSLATION_ANIMATION_TYPE_NONE:
			break;

		case TRANSLATION_ANIMATION_TYPE_MOVE:
			position += animationDeltaPosition * updateInfomation.deltaTime;

			animationPositionTime -= updateInfomation.deltaTime;
			if(animationPositionTime <= 0)
			{
				position = animationTargetPosition;
				translationAnimationType = TRANSLATION_ANIMATION_TYPE_NONE;
			}
			break;

		case TRANSLATION_ANIMATION_TYPE_CHASE:
			{
				const Vector2 delta = animationTargetPosition - position;

				position += delta * pow(animationPositionTime, updateInfomation.stepCount);

				if(delta.squareLength() <= 1.0f * 1.0f)
				{
					position = animationTargetPosition;
					translationAnimationType = TRANSLATION_ANIMATION_TYPE_NONE;
				}
			}
			break;
		}

		// オフセットで操作する位置アニメーションの更新
		{
			offset.zero();

			// ジャンプ
			if(jumpTime > 0.0f)
			{
				offset.y -= jumpHeight * sinf(jumpTime * jumpAngle);
				jumpTime -= updateInfomation.deltaTime;
			}

			// 振動
			if(shakeTime > 0.0f)
			{
				offset.x += (float)(rand()) / (float)(RAND_MAX);
				offset.y += (float)(rand()) / (float)(RAND_MAX);

				float amplitude = shakeAmplitude * (shakeTime / shakeLength);
				offset.x *= amplitude;
				offset.y *= amplitude;

				shakeTime -= updateInfomation.deltaTime;
			}
		}

		// スケールアニメーションの更新
		if(flags & COMPONENT_FLAGS_SCALE_ANIMATION_BIT)
		{
			scale += animationDeltaScale * updateInfomation.deltaTime;

			scaleTime -= updateInfomation.deltaTime;
			if(scaleTime <= 0)
			{
				scale = animationTargetScale;
				flags &= ~COMPONENT_FLAGS_SCALE_ANIMATION_BIT;
			}
		}

		// カラーアニメーションの更新
		if(flags & COMPONENT_FLAGS_COLOR_ANIMATION_BIT)
		{
			color += fadeDeltaColor * updateInfomation.deltaTime;

			fadeColorTime -= updateInfomation.deltaTime;
			if(fadeColorTime <= 0)
			{
				color = fadeTargetColor;
				flags &= ~COMPONENT_FLAGS_COLOR_ANIMATION_BIT;
			}
		}
#endif
	}

	/*!
	 * 描画イベント
	 */
	bool Object::onDraw(const DrawRect& drawRect)
	{
		// 描画不要？
		if(getVisible() == false || getOwnerDraw() == true)
			return false;
		Color baseColor = getDrawColor(drawRect);
		if(baseColor.a <= 0.0f)
			return false;

		// 描画すべき四点を求める
		const Vector2 p1 = drawRect.getDrawLeftTopPosition();
		const Vector2 p2 = drawRect.getDrawRightBottomPosition();
		if(!Desktop::getInstance().rangeOfVisibility(p1, p2))
			return false;
		const Vector2 p3(p2.x, p1.y);
		const Vector2 p4(p1.x, p2.y);

		if(texture)
			texture->bind();

		GraphicDevice::setBlendMode(getBlendMode());
		GraphicDevice::setColor(baseColor);

		unsigned char primitiveType = 0;

		// シザリング
		Vector2 lastScissorPosition;
		Vector2 lastScissorSize;
		if(getClipping())
		{
			GraphicDevice::getScissor(lastScissorPosition, lastScissorSize);
			GraphicDevice::setScissor(drawRect.clippingLeftTop, drawRect.getClippingSize());
			primitiveType = GraphicDevice::TYPE_SCISSOR;
		}
		if(getOpaque())
			primitiveType |= GraphicDevice::TYPE_OPAQUE;
		if(texture)
			primitiveType |= GraphicDevice::TYPE_TEXTURE_COORDINATE;

		GraphicDevice::begin(primitiveType, GraphicDevice::TRIANGLES, baseColor);

		if(texture)
		{
			const Vector2 textureBaseSize = texture->getTextureSize();
			const float u = texturePosition.x / textureBaseSize.x;
			const float v = texturePosition.y / textureBaseSize.y;
			const float w = textureSize.x / textureBaseSize.x;
			const float h = textureSize.y / textureBaseSize.y;

			GraphicDevice::addTextureCoord(Vector2(u    , v    ));
			GraphicDevice::addTextureCoord(Vector2(u + w, v    ));
			GraphicDevice::addTextureCoord(Vector2(u    , v + h));
			GraphicDevice::addTextureCoord(Vector2(u + w, v    ));
			GraphicDevice::addTextureCoord(Vector2(u    , v + h));
			GraphicDevice::addTextureCoord(Vector2(u + w, v + h));
		}

		GraphicDevice::addVertex(p1);
		GraphicDevice::addVertex(p3);
		GraphicDevice::addVertex(p4);
		GraphicDevice::addVertex(p3);
		GraphicDevice::addVertex(p4);
		GraphicDevice::addVertex(p2);

		GraphicDevice::end();
#if 0
		if(drawDebugInfomation)
		{
			primitiveType &= GraphicDevice::TYPE_SCISSOR;

			GraphicDevice::begin(primitiveType, GraphicDevice::TRIANGLES, Color(1.0f, 0.0f, 0.0f, 0.1f));
			GraphicDevice::addVertex(p1);
			GraphicDevice::addVertex(p3);
			GraphicDevice::addVertex(p4);
			GraphicDevice::addVertex(p3);
			GraphicDevice::addVertex(p4);
			GraphicDevice::addVertex(p2);
			GraphicDevice::end();

			GraphicDevice::begin(primitiveType|GraphicDevice::TYPE_OPAQUE, GraphicDevice::LINE_LOOP, Color(1.0f, 0.0f, 0.0f, 1.0f));
			GraphicDevice::addVertex(p1);
			GraphicDevice::addVertex(p3);
			GraphicDevice::addVertex(p2);
			GraphicDevice::addVertex(p4);
			GraphicDevice::end();
		}
#endif
		if(getClipping())
		{
			GraphicDevice::setScissor(lastScissorPosition, lastScissorSize);
		}

		return true;
	}

#if 0
	mana_stream_push_unsigned_integer(stream, type);
	mana_stream_push_unsigned_integer(stream, identification);
	type = mana_stream_pop_unsigned_integer(stream);
	identification = mana_stream_pop_unsigned_integer(stream);
#endif
}
